Atklājiet TypeScript uzlaboto tipu manipulācijas spēku. Šī rokasgrāmata pēta nosacītu tipus, kartētus tipus, inferenci un citus, lai veidotu robustas, mērogojamas un viegli uzturamas globālās programmatūras sistēmas.
Tipu manipulācija: Uzlabotas tipu transformācijas tehnikas robustai programmatūras dizainai
Mūsdienu programmatūras izstrādes mainīgajā vidē tipu sistēmas ieņem arvien svarīgāku lomu noturīgu, uzturamu un mērogojamu lietojumprogrammu veidošanā. Īpaši TypeScript ir kļuvis par dominējo spēku, paplašinot JavaScript ar jaudīgām statiskās tipizācijas iespējām. Lai gan daudzi izstrādātāji ir pazīstami ar pamata tipu deklarācijām, patiesais TypeScript spēks slēpjas tā uzlabotajās tipu manipulācijas funkcijās – tehnikās, kas ļauj dinamiski transformēt, paplašināt un atvasināt jaunus tipus no esošajiem. Šīs iespējas pārvieto TypeScript aiz vienkāršas tipu pārbaudes uz jomu, ko bieži dēvē par "tipu līmeņa programmēšanu".
Šī visaptverošā rokasgrāmata iedziļinās uzlaboto tipu transformācijas tehniku sarežģītajā pasaulē. Mēs izpētīsim, kā šie jaudīgie rīki var uzlabot jūsu kodu bāzi, palielināt izstrādātāju produktivitāti un uzlabot jūsu programmatūras kopējo robustumu, neatkarīgi no jūsu komandas atrašanās vietas vai konkrētās jomas, kurā strādājat. Sākot ar sarežģītu datu struktūru refaktorēšanu un beidzot ar ļoti paplašināmu bibliotēku izveidi, tipu manipulācijas apgūšana ir būtiska prasme jebkuram nopietnam TypeScript izstrādātājam, kurš tiecas uz izcilību globālā izstrādes vidē.
Tipu manipulācijas būtība: Kāpēc tā ir svarīga
Pēc būtības tipu manipulācija ir saistīta ar elastīgu un adaptīvu tipu definīciju izveidi. Iedomājieties scenāriju, kurā jums ir pamata datu struktūra, bet dažādām jūsu lietojumprogrammas daļām nepieciešamas nedaudz modificētas tās versijas – varbūt dažas īpašības ir jāpadara par neobligātām (optional), citas par tikai lasāmām (readonly), vai jāizvelk daži īpašību apakškopu. Tā vietā, lai manuāli dublētu un uzturētu vairākas tipu definīcijas, tipu manipulācija ļauj programmatiski ģenerēt šīs variācijas. Šī pieeja piedāvā vairākas dziļas priekšrocības:
- Samazināts rutīnas darbs (Boilerplate): Izvairieties no atkārtotu tipu definīciju rakstīšanas. Viena pamata tipa no tā var atvasināt daudzus atvasinātus.
- Uzlabota uzturēšana: Izmaiņas pamata tipā automātiski izplatās uz visiem atvasinātajiem tipiem, samazinot nekonsekvitāšu un kļūdu risku lielā kodu bāzē. Tas ir īpaši svarīgi globāli izplatītām komandām, kur nepareiza saziņa var novest pie atšķirīgām tipu definīcijām.
- Uzlabota tipu drošība: Sistēmiski atvasinot tipus, jūs nodrošināt augstāku tipu pareizības pakāpi visā jūsu lietojumprogrammā, uztverot potenciālās kļūdas kompilācijas laikā, nevis izpildes laikā.
- Lielāka elastība un paplašināmība: Dizainējiet API un bibliotēkas, kas ir ļoti adaptīvas dažādiem lietošanas gadījumiem, nezaudējot tipu drošību. Tas ļauj izstrādātājiem visā pasaulē ar pārliecību integrēt jūsu risinājumus.
- Labāka izstrādātāju pieredze: Viedā tipu inference un automātiskā pabeigšana kļūst precīzāka un noderīgāka, paātrinot izstrādi un samazinot kognitīvo slodzi, kas ir universāls ieguvums visiem izstrādātājiem.
Sāksim šo ceļojumu, lai atklātu uzlabotās tehnikas, kas padara tipu līmeņa programmēšanu tik transformējošu.
Pamata tipu transformācijas celtniecības bloki: Utility Types
TypeScript nodrošina iebūvētu "Utility Types" kopumu, kas kalpo kā pamata rīki biežiem tipu transformācijām. Tie ir lieliski sākumpunkti, lai saprastu tipu manipulācijas principus pirms savu sarežģīto transformāciju izveides.
1. Partial<T>
Šis utilitārais tips veido tipu ar visām T īpašībām, kas iestatītas kā neobligātas (optional). Tas ir neticami noderīgi, ja jums ir jāizveido tips, kas attēlo esoša objekta īpašību apakškopu, bieži vien atjaunināšanas operācijām, kur ne visas laukumi ir nodrošināti.
Piemērs:
interface UserProfile { id: string; username: string; email: string; country: string; avatarUrl?: string; }
type PartialUserProfile = Partial<UserProfile>; /* Līdzvērtīgi: type PartialUserProfile = { id?: string; username?: string; email?: string; country?: string; avatarUrl?: string; }; */
const updateUserData: PartialUserProfile = { email: 'new.email@example.com' }; const newUserData: PartialUserProfile = { username: 'global_user_X', country: 'Germany' };
2. Required<T>
Gluži pretēji, Required<T> veido tipu, kas sastāv no visām T īpašībām, kas iestatītas kā obligātas (required). Tas ir noderīgi, ja jums ir interfeiss ar neobligātām īpašībām, bet noteiktā kontekstā jūs zināt, ka šīs īpašības vienmēr būs klāt.
Piemērs:
interface Configuration { timeout?: number; retries?: number; apiKey: string; }
type StrictConfiguration = Required<Configuration>; /* Līdzvērtīgi: type StrictConfiguration = { timeout: number; retries: number; apiKey: string; }; */
const defaultConfiguration: StrictConfiguration = { timeout: 5000, retries: 3, apiKey: 'XYZ123' };
3. Readonly<T>
Šis utilitārais tips veido tipu ar visām T īpašībām, kas iestatītas kā tikai lasāmas (readonly). Tas ir nenovērtējams, lai nodrošinātu nemainīgumu (immutability), īpaši, nododot datus funkcijām, kurām nevajadzētu modificēt sākotnējo objektu, vai dizainējot stāvokļa pārvaldības sistēmas.
Piemērs:
interface Product { id: string; name: string; price: number; }
type ImmutableProduct = Readonly<Product>; /* Līdzvērtīgi: type ImmutableProduct = { readonly id: string; readonly name: string; readonly price: number; }; */
const catalogItem: ImmutableProduct = { id: 'P001', name: 'Global Widget', price: 99.99 }; // catalogItem.name = 'New Name'; // Kļūda: Nevar piešķirt "name", jo tā ir tikai lasāma īpašība.
4. Pick<T, K>
Pick<T, K> veido tipu, izvēloties īpašību kopu K (virknējo literāļu apvienojumu) no T. Tas ir ideāli piemērots īpašību apakškopu izvilkšanai no lielāka tipa.
Piemērs:
interface Employee { id: string; name: string; department: string; salary: number; email: string; }
type EmployeeOverview = Pick<Employee, 'name' | 'department' | 'email'>; /* Līdzvērtīgi: type EmployeeOverview = { name: string; department: string; email: string; }; */
const hrView: EmployeeOverview = { name: 'Javier Garcia', department: 'Human Resources', email: 'javier.g@globalcorp.com' };
5. Omit<T, K>
Omit<T, K> veido tipu, izvēloties visas T īpašības un pēc tam noņemot K (virknējo literāļu apvienojumu). Tas ir Pick<T, K> pretstats un tikpat noderīgs, lai izveidotu atvasinātus tipus ar izslēgtām noteiktām īpašībām.
Piemērs:
interface Employee { /* tāda pati kā iepriekš */ }
type EmployeePublicProfile = Omit<Employee, 'salary' | 'id'>; /* Līdzvērtīgi: type EmployeePublicProfile = { name: string; department: string; email: string; }; */
const publicInfo: EmployeePublicProfile = { name: 'Javier Garcia', department: 'Human Resources', email: 'javier.g@globalcorp.com' };
6. Exclude<T, U>
Exclude<T, U> veido tipu, izslēdzot no T visus apvienojuma locekļus, kas ir piešķirami U. Tas galvenokārt ir paredzēts apvienojuma tipiem.
Piemērs:
type EventStatus = 'pending' | 'processing' | 'completed' | 'failed' | 'cancelled'; type ActiveStatus = Exclude<EventStatus, 'completed' | 'failed' | 'cancelled'>; /* Līdzvērtīgi: type ActiveStatus = "pending" | "processing"; */
7. Extract<T, U>
Extract<T, U> veido tipu, izvelkot no T visus apvienojuma locekļus, kas ir piešķirami U. Tas ir Exclude<T, U> pretstats.
Piemērs:
type AllDataTypes = string | number | boolean | string[] | { key: string }; type ObjectTypes = Extract<AllDataTypes, object>; /* Līdzvērtīgi: type ObjectTypes = string[] | { key: string }; */
8. NonNullable<T>
NonNullable<T> veido tipu, izslēdzot null un undefined no T. Noderīgi, lai stingri definētu tipus, kur null vai undefined vērtības nav paredzamas.
Piemērs:
type NullableString = string | null | undefined; type CleanString = NonNullable<NullableString>; /* Līdzvērtīgi: type CleanString = string; */
9. Record<K, T>
Record<K, T> veido objektu tipu, kura īpašību atslēgas ir K un kuru īpašību vērtības ir T. Tas ir jaudīgs, lai izveidotu vārdnīcai līdzīgus tipus.
Piemērs:
type Countries = 'USA' | 'Japan' | 'Brazil' | 'Kenya'; type CurrencyMapping = Record<Countries, string>; /* Līdzvērtīgi: type CurrencyMapping = { USA: string; Japan: string; Brazil: string; Kenya: string; }; */
const countryCurrencies: CurrencyMapping = { USA: 'USD', Japan: 'JPY', Brazil: 'BRL', Kenya: 'KES' };
Šie utilitārie tipi ir pamata. Tie demonstrē koncepciju, ka viena tipa pārveidošana citā, pamatojoties uz iepriekš definētiem noteikumiem. Tagad aplūkosim, kā mēs paši varam izveidot šādus noteikumus.
Nosacīti tipi: "Ja-citādi" spēks tipu līmenī
Nosacīti tipi ļauj definēt tipu, kas ir atkarīgs no nosacījuma. Tie ir analogi nosacījumu (ternāriem) operatoriem JavaScript (condition ? trueExpression : falseExpression), bet darbojas ar tipiem. Sintakse ir T extends U ? X : Y.
Tas nozīmē: ja tips T ir piešķirams tipam U, tad rezultējošais tips ir X; pretējā gadījumā tas ir Y.
Nosacīti tipi ir viena no visspēcīgākajām funkcijām uzlabotai tipu manipulācijai, jo tie ievieš loģiku tipu sistēmā.
Pamata piemērs:
Reimplementēsim vienkāršotu NonNullable:
type MyNonNullable<T> = T extends null | undefined ? never : T;
type Result1 = MyNonNullable<string | null>; // string type Result2 = MyNonNullable<number | undefined>; // number type Result3 = MyNonNullable<boolean>; // boolean
Šeit, ja T ir null vai undefined, tas tiek noņemts (attēlots ar never, kas efektīvi to noņem no apvienojuma tipa). Pretējā gadījumā T paliek.
Distributīvi nosacīti tipi:
Svarīga nosacītu tipu izturēšanās ir to distributivitāte pār apvienojuma tipiem. Kad nosacīts tips darbojas uz kailu tipa parametru (tipa parametrs, kas nav iesaiņots citā tipā), tas izplatās pār apvienojuma locekļiem. Tas nozīmē, ka nosacītais tips tiek piemērots katram apvienojuma loceklim atsevišķi, un rezultāti pēc tam tiek apvienoti jaunā apvienojumā.
Distributivitātes piemērs:
Apsveriet tipu, kas pārbauda, vai tips ir virkne vai skaitlis:
type IsStringOrNumber<T> = T extends string | number ? 'stringOrNumber' : 'other';
type Test1 = IsStringOrNumber<string>; // "stringOrNumber" type Test2 = IsStringOrNumber<boolean>; // "other" type Test3 = IsStringOrNumber<string | boolean>; // "stringOrNumber" | "other" (jo tas izplatās)
Bez distributivitātes Test3 pārbaudītu, vai string | boolean ir piešķirams string | number (kas tas nav pilnībā), potenciāli novedot pie "other". Bet, tā kā tas izplatās, tas atsevišķi novērtē string extends string | number ? ... : ... un boolean extends string | number ? ... : ..., pēc tam apvieno rezultātus.
Praktiska lietošana: tipu apvienojumu izlīdzināšana
Pieņemsim, ka jums ir objektu apvienojums un vēlaties izvilkt kopīgas īpašības vai apvienot tās noteiktā veidā. Nosacīti tipi ir galvenie.
type Flatten<T> = T extends infer R ? { [K in keyof R]: R[K] } : never;
Lai gan šis vienkāršais Flatten var pats par sevi nedarīt daudz, tas ilustrē, kā nosacītu tipu var izmantot kā "trigeri" distributivitātei, īpaši, ja tas ir apvienots ar infer atslēgvārdu, ko mēs apskatīsim tālāk.
Nosacīti tipi nodrošina izsmalcinātu tipu līmeņa loģiku, padarot tos par uzlaboto tipu transformāciju stūrakmeni. Tie bieži tiek apvienoti ar citām tehnikām, īpaši infer atslēgvārdu.
Inferēšana nosacītos tipos: 'infer' atslēgvārds
infer atslēgvārds ļauj deklarēt tipu mainīgo nosacīta tipa extends rindā. Šo mainīgo var pēc tam izmantot, lai "uzķertu" tipu, kas tiek saskaņots, padarot to pieejamu nosacītā tipa patiesajā zarā. Tas ir kā atbilstības meklēšana (pattern matching) tipiem.
Sintakse: T extends SomeType<infer U> ? U : FallbackType;
Tas ir neticami jaudīgi, lai dekonstruētu tipus un izvilktu to noteiktas daļas. Apskatīsim dažus pamata utilitāros tipus, kas atkārtoti ieviesti ar infer, lai saprastu tā mehānismu.
1. ReturnType<T>
Šis utilitārais tips izvelk funkcijas tipa atgriešanas tipu. Iedomājieties, ka jums ir globāls utilitāro funkciju kopums un jums ir jāzina precīzs datu tips, ko tās ražo, neizsaucot tās.
Oficiālā ieviešana (vienkāršota):
type MyReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Piemērs:
function getUserData(userId: string): { id: string; name: string; email: string } { return { id: userId, name: 'John Doe', email: 'john.doe@example.com' }; }
type UserDataType = MyReturnType<typeof getUserData>; /* Līdzvērtīgi: type UserDataType = { id: string; name: string; email: string; }; */
2. Parameters<T>
Šis utilitārais tips izvelk funkcijas tipa parametru tipus kā tupu. Būtiski, lai izveidotu tipu drošus apvalkus vai dekoratorus.
Oficiālā ieviešana (vienkāršota):
type MyParameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;
Piemērs:
function sendNotification(userId: string, message: string, priority: 'low' | 'medium' | 'high'): boolean { console.log(`Sending notification to ${userId}: ${message} with priority ${priority}`); return true; }
type NotificationArgs = MyParameters<typeof sendNotification>; /* Līdzvērtīgi: type NotificationArgs = [userId: string, message: string, priority: 'low' | 'medium' | 'high']; */
3. UnpackPromise<T>
Šis ir izplatīts pielāgots utilitārais tips darbam ar asinhronām operācijām. Tas izvelk promisa (Promise) izšķirtās vērtības tipu.
type UnpackPromise<T> = T extends Promise<infer U> ? U : T;
Piemērs:
async function fetchConfig(): Promise<{ apiBaseUrl: string; timeout: number }> { return { apiBaseUrl: 'https://api.globalapp.com', timeout: 60000 }; }
type ConfigType = UnpackPromise<ReturnType<typeof fetchConfig>>; /* Līdzvērtīgi: type ConfigType = { apiBaseUrl: string; timeout: number; }; */
infer atslēgvārds, apvienojumā ar nosacītiem tipiem, nodrošina mehānismu sarežģītu tipu izpētei un to daļu izvilkšanai, veidojot pamatu daudzām uzlabotām tipu transformācijām.
Kartēti tipi: Objektu formu sistemātiska transformācija
Kartēti tipi ir jaudīga funkcija jaunu objektu tipu izveidošanai, transformējot esošā objektu tipa īpašības. Tie iterē pār dotā tipa atslēgām un piemēro transformāciju katrai īpašībai. Sintakse parasti izskatās kā [P in K]: T[P], kur K parasti ir keyof T.
Pamata sintakse:
type MyMappedType<T> = { [P in keyof T]: T[P]; // Šeit faktiski nav transformācijas, tikai īpašību kopēšana };
Šī ir pamata struktūra. Maģija notiek, kad jūs modificējat īpašību vai vērtības tipu iekavās.
Piemērs: `Readonly
type MyReadonly<T> = { readonly [P in keyof T]: T[P]; };
Piemērs: `Partial
type MyPartial<T> = { [P in keyof T]?: T[P]; };
? pēc P in keyof T padara īpašību neobligātu. Tāpat var noņemt neobligātumu ar -[P in keyof T]?: T[P] un noņemt readonly ar -readonly [P in keyof T]: T[P].
Atslēgu pārdēvēšana ar "as" klauzulu:
TypeScript 4.1 ieviesa as klauzulu kartētos tipos, kas ļauj pārdēvēt īpašību atslēgas. Tas ir neticami noderīgi, lai transformētu īpašību nosaukumus, piemēram, pievienojot prefiksus/sufiksus, mainot burtu lielumu vai filtrējot atslēgas.
Sintakse: [P in K as NewKeyType]: T[P];
Piemērs: atslēgu prefiksu pievienošana visām atslēgām
type EventPayload = { userId: string; action: string; timestamp: number; };
type PrefixedPayload<T> = { [K in keyof T as `event${Capitalize<string & K>}`]: T[K]; };
type TrackedEvent = PrefixedPayload<EventPayload>; /* Līdzvērtīgi: type TrackedEvent = { eventUserId: string; eventAction: string; eventTimestamp: number; }; */
Šeit Capitalize<string & K> ir Veidņu Literālu Tips (Template Literal Type) (apskatīts tālāk), kas palielina atslēgas pirmā burta lielumu. string & K nodrošina, ka K tiek apstrādāts kā virknes literālis Capitalize utilitātei.
Īpašumu filtrēšana kartēšanas laikā:
Jūs varat arī izmantot nosacītus tipus as klauzulā, lai filtrētu īpašības vai pārdēvētu tās nosacīti. Ja nosacītais tips tiek izšķirts uz never, īpašība tiek izslēgta no jaunā tipa.
Piemērs: izslēgt īpašības ar noteiktu tipu
type Config = { appName: string; version: number; debugMode: boolean; apiEndpoint: string; };
type StringProperties<T> = { [K in keyof T as T[K] extends string ? K : never]: T[K]; };
type AppStringConfig = StringProperties<Config>; /* Līdzvērtīgi: type AppStringConfig = { appName: string; apiEndpoint: string; }; */
Kartēti tipi ir neticami universāli objektu formu transformēšanai, kas ir bieži sastopama prasība datu apstrādē, API dizainā un komponentu rekvizītu pārvaldībā dažādos reģionos un platformās.
Veidņu literālu tipi: Virkņu manipulācija tipiem
Ieviesti TypeScript 4.1, Veidņu Literālu Tipi (Template Literal Types) atnes JavaScript veidņu virkņu literāļu spēku uz tipu sistēmu. Tie ļauj konstruēt jaunus virkņu literālu tipus, savienojot virkņu literāļus ar apvienojuma tipiem un citiem virkņu literālu tipiem. Šī funkcija paver plašas iespējas, lai izveidotu tipus, kas balstīti uz noteiktiem virkņu paraugiem.
Sintakse: Backticks (`) tiek izmantoti, tāpat kā JavaScript veidņu literāļi, lai iegultu tipus vietturos (${Type}).
Piemērs: pamata savienošana
type Greeting = 'Hello'; type Name = 'World' | 'Universe'; type FullGreeting = `${Greeting} ${Name}!`; /* Līdzvērtīgi: type FullGreeting = "Hello World!" | "Hello Universe!"; */
Tas jau ir diezgan jaudīgs, lai ģenerētu virkņu literāļu apvienojumus, pamatojoties uz esošiem virkņu literālu tipiem.
Iebūvētie virkņu manipulācijas utilitārie tipi:
TypeScript nodrošina arī četrus iebūvētus utilitāros tipus, kas izmanto veidņu literālu tipus biežām virkņu transformācijām:
- Capitalize<S>: Pārvērš virkņu literālu tipa pirmo burtu tā augšējā (uppercase) ekvivalentā.
- Lowercase<S>: Pārvērš katru virkņu literālu tipa rakstzīmi tā apakšējā (lowercase) ekvivalentā.
- Uppercase<S>: Pārvērš katru virkņu literālu tipa rakstzīmi tā augšējā (uppercase) ekvivalentā.
- Uncapitalize<S>: Pārvērš virkņu literālu tipa pirmo burtu tā apakšējā (lowercase) ekvivalentā.
Piemēra lietošana:
type Locale = 'en-US' | 'fr-CA' | 'ja-JP'; type EventAction = 'click' | 'hover' | 'submit';
type EventID = `${Uppercase<EventAction>}_${Capitalize<Locale>}`; /* Līdzvērtīgi: type EventID = "CLICK_En-US" | "CLICK_Fr-CA" | "CLICK_Ja-JP" | "HOVER_En-US" | "HOVER_Fr-CA" | "HOVER_Ja-JP" | "SUBMIT_En-US" | "SUBMIT_Fr-CA" | "SUBMIT_Ja-JP"; */
Tas parāda, kā jūs varat ģenerēt sarežģītus virkņu literāļu apvienojumus starptautiski lokalizētiem notikumu ID, API galapunktiem vai CSS klases nosaukumiem tipu drošā veidā.
Apvienošana ar kartētiem tipiem dinamiskiem atslēgas nosaukumiem:
Veidņu literālu tipu patiesais spēks bieži vien spīd, kad tie ir apvienoti ar kartētiem tipiem un as klauzulu atslēgu pārdēvēšanai.
Piemērs: izveidot Getter/Setter tipus objektam
interface Settings { theme: 'dark' | 'light'; notificationsEnabled: boolean; }
type GetterSetters<T> = { [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]; } & { [K in keyof T as `set${Capitalize<string & K>}`]: (value: T[K]) => void; };
type SettingsAPI = GetterSetters<Settings>; /* Līdzvērtīgi: type SettingsAPI = { getTheme: () => "dark" | "light"; getNotificationsEnabled: () => boolean; } & { setTheme: (value: "dark" | "light") => void; setNotificationsEnabled: (value: boolean) => void; }; */
Šī transformācija ģenerē jaunu tipu ar metodēm, piemēram, getTheme(), setTheme('dark') utt., tieši no jūsu pamata Settings interfeisa, viss ar spēcīgu tipu drošību. Tas ir nenovērtējami, lai ģenerētu stingri tipizētas klientu saskarnes backend API vai konfigurācijas objektiem.
Rekursīvas tipu transformācijas: ligzdotu struktūru apstrāde
Daudzas reālās datu struktūras ir dziļi ligzdotas. Domājiet par sarežģītiem JSON objektiem, kas atgriezti no API, konfigurācijas kokiem vai ligzdotām komponentu rekvizītiem. Tipu transformāciju piemērošana šīm struktūrām bieži prasa rekursīvu pieeju. TypeScript tipu sistēma atbalsta rekursiju, ļaujot definēt tipus, kas atsaucas uz sevi, tādējādi nodrošinot transformācijas, kas var iziet un modificēt tipus jebkurā dziļumā.
Tomēr tipu līmeņa rekursijai ir ierobežojumi. TypeScript ir rekursijas dziļuma ierobežojums (bieži apmēram 50 līmeņi, lai gan tas var atšķirties), virs kura tas kļūdīsies, lai novērstu bezgalīgus tipu aprēķinus. Ir svarīgi rūpīgi projektēt rekursīvus tipus, lai izvairītos no šo ierobežojumu sasniegšanas vai bezgalīgās cilpās.
Piemērs: DeepReadonly<T>
Kamēr Readonly<T> padara objekta tiešās īpašības par tikai lasāmām, tas to nerekursīvi nepiemēro ligzdotajiem objektiem. Lai nodrošinātu patiesi nemainīgu struktūru, jums ir nepieciešams DeepReadonly.
type DeepReadonly<T> = T extends object ? { readonly [K in keyof T]: DeepReadonly<T[K]>; } : T;
Sadārdzināsim to:
- T extends object ? ... : T;: Šis ir nosacīts tips. Tas pārbauda, vai T ir objekts (vai masīvs, kas arī ir objekts JavaScript). Ja tas nav objekts (ti, tas ir primitīvs, piemēram, string, number, boolean, null, undefined vai funkcija), tas vienkārši atgriež T pašu, jo primitīvie tipi ir pēc būtības nemainīgi.
- { readonly [K in keyof T]: DeepReadonly<T[K]>; }: Ja T ir objekts, tas piemēro kartētu tipu.
- readonly [K in keyof T]: Tas iterē pār katru īpašību K tipā T un atzīmē to kā readonly.
- DeepReadonly<T[K]>: Kritiski svarīgā daļa. Katrai īpašības vērtībai T[K], tas rekursīvi izsauc DeepReadonly. Tas nodrošina, ka, ja T[K] pats ir objekts, process atkārtojas, padarot tā ligzdotās īpašības arī par tikai lasāmām.
Piemēra lietošana:
interface UserSettings { theme: 'dark' | 'light'; notifications: { email: boolean; sms: boolean; }; preferences: string[]; }
type ImmutableUserSettings = DeepReadonly<UserSettings>; /* Līdzvērtīgi: type ImmutableUserSettings = { readonly theme: "dark" | "light"; readonly notifications: { readonly email: boolean; readonly sms: boolean; }; readonly preferences: readonly string[]; // Masīva elementi nav tikai lasāmi, bet pats masīvs ir. }; */
const userConfig: ImmutableUserSettings = { theme: 'dark', notifications: { email: true, sms: false }, preferences: ['darkMode', 'notifications'] };
// userConfig.theme = 'light'; // Kļūda! // userConfig.notifications.email = false; // Kļūda! // userConfig.preferences.push('locale'); // Kļūda! (Masīva atsaucei, nevis tā elementiem)
Piemērs: DeepPartial<T>
Līdzīgi kā DeepReadonly, DeepPartial padara visas īpašības, ieskaitot ligzdotu objektu īpašības, par neobligātām.
type DeepPartial<T> = T extends object ? { [K in keyof T]?: DeepPartial<T[K]>; } : T;
Piemēra lietošana:
interface PaymentDetails { card: { number: string; expiry: string; }; billingAddress: { street: string; city: string; zip: string; country: string; }; }
type PaymentUpdate = DeepPartial<PaymentDetails>; /* Līdzvērtīgi: type PaymentUpdate = { card?: { number?: string; expiry?: string; }; billingAddress?: { street?: string; city?: string; zip?: string; country?: string; }; }; */
const updateAddress: PaymentUpdate = { billingAddress: { country: 'Canada', zip: 'A1B 2C3' } };
Rekursīvi tipi ir būtiski, lai apstrādātu sarežģītus, hierarhiskus datu modeļus, kas bieži sastopami uzņēmumu lietojumprogrammās, API atbildēs un globālo sistēmu konfigurācijas pārvaldībā, ļaujot precīzi definēt tipus nepilnīgām atjaunināšanām vai nemainīgām stāvokļa dziļajām struktūrām.
Tipu aizsargi (Type Guards) un apstiprinājuma funkcijas (Assertion Functions): Runtime tipu precizēšana
Lai gan tipu manipulācija galvenokārt notiek kompilācijas laikā, TypeScript piedāvā arī mehānismus tipu precizēšanai izpildes laikā: Tipu aizsargi (Type Guards) un Apstiprinājuma Funkcijas (Assertion Functions). Šīs funkcijas savieno statisko tipu pārbaudi un dinamisku JavaScript izpildi, ļaujot jums sašaurināt tipus, pamatojoties uz izpildes laika pārbaudēm, kas ir ļoti svarīgi, lai apstrādātu daudzveidīgus datu ieejas no dažādiem avotiem visā pasaulē.
Tipu aizsargi (Predicate Functions)
Tipu aizargs ir funkcija, kas atgriež boolean vērtību un kuras atgriešanas tips ir tipa predikāts. Tipa predikāts ir formāts parameterName is Type. Kad TypeScript redz izsaukto tipu aizsargu, tas izmanto rezultātu, lai sašaurinātu mainīgā tipu attiecīgajā tvērumā.
Piemērs: diskriminējošu apvienojumu tipu izmantošana
interface SuccessResponse { status: 'success'; data: any; } interface ErrorResponse { status: 'error'; message: string; code: number; } type ApiResponse = SuccessResponse | ErrorResponse;
function isSuccessResponse(response: ApiResponse): response is SuccessResponse { return response.status === 'success'; }
function handleResponse(response: ApiResponse) { if (isSuccessResponse(response)) { console.log('Data received:', response.data); // "response" tagad ir zināms kā SuccessResponse } else { console.error('Error occurred:', response.message, 'Code:', response.code); // "response" tagad ir zināms kā ErrorResponse } }
Tipu aizsargi ir pamata, lai droši strādātu ar apvienojumu tipiem, īpaši, apstrādājot datus no ārējiem avotiem, piemēram, API, kas var atgriezt atšķirīgas struktūras, pamatojoties uz panākumiem vai kļūmēm, vai dažādiem ziņojumu tipiem globālā notikumu buss.
Apstiprinājuma funkcijas
Ieviestas TypeScript 3.7, apstiprinājuma funkcijas ir līdzīgas tipu aizsargiem, bet tiem ir cits mērķis: apstiprināt, ka nosacījums ir patiess, un, ja nav, izraisīt kļūdu. To atgriešanas tips izmanto asserts condition sintaksi. Kad funkcija ar asserts parakstu atgriežas bez izsaukšanas, TypeScript sašaurina argumenta tipu, pamatojoties uz apstiprinājumu.
Piemērs: apstiprināt nenullošanos (Non-Nullability)
function assertIsDefined<T>(val: T, message?: string): asserts val is NonNullable<T> { if (val === undefined || val === null) { throw new Error(message || 'Value must be defined'); } }
function processConfig(config: { baseUrl?: string; retries?: number }) { assertIsDefined(config.baseUrl, 'Base URL is required for configuration'); // Pēc šīs rindas config.baseUrl ir garantēts kā 'string', nevis 'string | undefined' console.log('Processing data from:', config.baseUrl.toUpperCase()); if (config.retries !== undefined) { console.log('Retries:', config.retries); } }
Apstiprinājuma funkcijas ir lieliskas, lai nodrošinātu priekšnosacījumus, validētu ievades un nodrošinātu, ka kritiski svarīgas vērtības ir klāt pirms darbības turpināšanas. Tas ir nenovērtējami, projektējot robustas sistēmas, īpaši ievades validācijai, kur dati var nākt no neuzticamiem avotiem vai lietotāja ievades veidlapām, kas paredzētas dažādiem globāliem lietotājiem.
Gan tipu aizsargi, gan apstiprinājuma funkcijas nodrošina dinamisku elementu TypeScript statiskajai tipu sistēmai, ļaujot izpildes laika pārbaudēm informēt kompilācijas laika tipus, tādējādi palielinot kopējo koda drošību un prognozējamību.
Reālās pasaules lietojumprogrammas un labākās prakses
Uzlaboto tipu transformācijas tehniku apgūšana nav tikai akadēmisks vingrinājums; tai ir dziļas praktiskas sekas augstas kvalitātes programmatūras veidošanā, īpaši globāli izplatītās izstrādes komandās.
1. Robustu API klientu ģenerēšana
Iedomājieties, ka patērējat REST vai GraphQL API. Tā vietā, lai manuāli rakstītu atbildes interfeisus katram galapunktam, jūs varat definēt pamata tipus un pēc tam izmantot kartētus, nosacītus un infer tipus, lai ģenerētu klientu puses tipus pieprasījumiem, atbildēm un kļūdām. Piemēram, tips, kas transformē GraphQL vaicājuma virkni pilnīgi tipizētā rezultāta objektā, ir galvenais uzlabotās tipu manipulācijas piemērs darbībā. Tas nodrošina konsekvitāti starp dažādiem klientiem un mikropakalpojumiem, kas izvietoti dažādos reģionos.
2. Karkasu un bibliotēku izstrāde
Galvenie karkasi, piemēram, React, Vue un Angular, vai utilitārās bibliotēkas, piemēram, Redux Toolkit, ļoti paļaujas uz tipu manipulāciju, lai nodrošinātu izcilu izstrādātāju pieredzi. Viņi izmanto šīs tehnikas, lai inferētu tipus rekvizītiem, stāvoklim, darbības veidotājiem un selektoriem, ļaujot izstrādātājiem rakstīt mazāk rutīnas darba, vienlaikus saglabājot spēcīgu tipu drošību. Šī paplašināmība ir ļoti svarīga bibliotēkām, ko pieņem globāla izstrādātāju kopiena.
3. Stāvokļa pārvaldība un nemainīgums (Immutability)
Lietojumprogrammās ar sarežģītu stāvokli nemainīguma nodrošināšana ir prognozējamas darbības atslēga. DeepReadonly tipi palīdz nodrošināt to kompilācijas laikā, novēršot nejaušas modifikācijas. Tāpat precīzu stāvokļa atjauninājumu tipu definēšana (piemēram, izmantojot DeepPartial ielāpu (patch) operācijām) var ievērojami samazināt kļūdas, kas saistītas ar stāvokļa konsistenci, kas ir būtiska lietojumprogrammām, kas apkalpo lietotājus visā pasaulē.
4. Konfigurācijas pārvaldība
Lietojumprogrammām bieži ir sarežģīti konfigurācijas objekti. Tipu manipulācija var palīdzēt definēt stingras konfigurācijas, piemērot vides specifiskus pārrakstījumus (piemēram, izstrādes pret ražošanas tipiem) vai pat ģenerēt konfigurācijas tipus, pamatojoties uz shēmas definīcijām. Tas nodrošina, ka dažādas izvietošanas vides, potenciāli dažādos kontinentos, izmanto konfigurācijas, kas atbilst stingriem noteikumiem.
5. Uz notikumiem balstītas arhitektūras
Sistēmās, kur notikumi plūst starp dažādiem komponentiem vai pakalpojumiem, skaidru notikumu tipu definēšana ir vissvarīgākā. Veidņu literālu tipi var ģenerēt unikālus notikumu ID (piemēram, USER_CREATED_V1), savukārt nosacīti tipi var palīdzēt atšķirt dažādas notikumu atbildes, nodrošinot robustu komunikāciju starp jūsu sistēmas vaļīgi savienotajām daļām.
Labākās prakses:
- Sāciet vienkārši: Nesodiniet pie vis sarežģītākā risinājuma uzreiz. Sāciet ar pamata utilitāriem tipiem un pievienojiet sarežģītību tikai tad, ja tas ir nepieciešams.
- Dokumentējiet rūpīgi: Uzlabotos tipus var būt grūti saprast. Izmantojiet JSDoc komentārus, lai izskaidrotu to mērķi, paredzētos ievadus un izvades. Tas ir ļoti svarīgi jebkurai komandai, īpaši tām ar dažādu valodu foniem.
- Testējiet savus tipus: Jā, jūs varat testēt tipus! Izmantojiet rīkus, piemēram, tsd (TypeScript Definition Tester), vai rakstiet vienkāršus piešķīrumus, lai pārbaudītu, vai jūsu tipi darbojas, kā paredzēts.
- Dodiet priekšroku atkārtotai lietošanai: Izveidojiet vispārīgus utilitārus tipus, kurus var atkārtoti izmantot visā jūsu kodu bāzē, nevis ad-hoc, vienreizējas tipu definīcijas.
- Līdzsvarojiet sarežģītību un skaidrību: Lai gan jaudīgi, pārmērīgi sarežģīta tipu maģija var kļūt par uzturēšanas slogu. Centieties sasniegt līdzsvaru, kurā tipu drošības priekšrocības pārsniedz tipu definīciju izpratnes kognitīvo slodzi.
- Uzraudziet kompilācijas veiktspēju: Ļoti sarežģīti vai dziļi rekursīvi tipi dažreiz var palēnināt TypeScript kompilāciju. Ja pamanāt veiktspējas pasliktināšanos, pārskatiet savas tipu definīcijas.
Papildu tēmas un nākotnes virzieni
Ceļojums uz tipu manipulāciju nebeidzas šeit. TypeScript komanda nepārtraukti ieviest jauninājumus, un kopiena aktīvi pēta vēl izsmalcinātākus konceptus.
Nominal vs. Strukturālā tipizācija
TypeScript ir strukturāli tipizēts, kas nozīmē, ka divi tipi ir savietojami, ja tiem ir tāda pati forma, neatkarīgi no to deklarētajiem nosaukumiem. Turpretī nominālā tipizācija (kas sastopama tādās valodās kā C# vai Java) uzskata tipus par savietojamiem tikai tad, ja tiem ir tāda paša deklarācija vai mantojuma ķēde. Lai gan TypeScript strukturālā daba bieži ir izdevīga, ir gadījumi, kad ir vēlama nomināla uzvedība (piemēram, lai novērstu UserID tipa piešķiršanu ProductID tipam, pat ja abi ir tikai string).
Tipu zīmolu tehnikas (type branding techniques), izmantojot unikālas simbolu īpašības vai literālu apvienojumus kopā ar krustojumu tipiem (intersection types), ļauj simulēt nominālo tipizāciju TypeScript. Šī ir uzlabota tehnika, lai radītu stingrākas atšķirības starp strukturāli identiskiem, bet konceptuāli atšķirīgiem tipiem.
Piemērs (vienkāršots):
type Brand<T, B> = T & { __brand: B }; type UserID = Brand<string, 'UserID'>; type ProductID = Brand<string, 'ProductID'>;
function getUser(id: UserID) { /* ... */ } function getProduct(id: ProductID) { /* ... */ }
const myUserId: UserID = 'user-123' as UserID; const myProductId: ProductID = 'prod-456' as ProductID;
getUser(myUserId); // OK // getUser(myProductId); // Kļūda: Tips 'ProductID' nav piešķirams tipam 'UserID'.
Tipu līmeņa programmēšanas paradigmas
Tā kā tipi kļūst arvien dinamiskāki un izteiksmīgāki, izstrādātāji pēta tipu līmeņa programmēšanas modeļus, kas atgādina funkcionālo programmēšanu. Tas ietver tehnikas tipu līmeņa sarakstiem, stāvokļa mašīnām un pat primitīviem kompilatoriem pilnībā tipu sistēmas ietvaros. Lai gan bieži vien pārmērīgi sarežģīti parastajam lietojumprogrammu kodam, šie pētījumi virza iespēju robežas un informē par nākotnes TypeScript funkcijām.
Secinājums
Uzlabotās tipu transformācijas tehnikas TypeScript ir vairāk nekā tikai sintaktisks cukurs; tās ir pamata rīki sarežģītu, noturīgu un uzturamu programmatūras sistēmu veidošanai. Izmantojot nosacītus tipus, kartētus tipus, infer atslēgvārdu, veidņu literālu tipus un rekursīvus modeļus, jūs iegūstat spēju rakstīt mazāk koda, uztvert vairāk kļūdu kompilācijas laikā un projektēt API, kas ir gan elastīgas, gan neticami robustas.
Tā kā programmatūras nozare turpina globalizēties, nepieciešamība pēc skaidrām, viennozīmīgām un drošām koda praksēm kļūst vēl kritiskāka. TypeScript uzlabotā tipu sistēma nodrošina universālu valodu datu struktūru un uzvedības definēšanai un piemērošanai, nodrošinot, ka komandas ar dažādiem foniem var efektīvi sadarboties un nodrošināt augstas kvalitātes produktus. Investējiet laiku, lai apgūtu šīs tehnikas, un jūs atklāsiet jaunu produktivitātes un pārliecības līmeni savā TypeScript izstrādes ceļojumā.
Kādas uzlabotas tipu manipulācijas jums ir izrādījušās visnoderīgākās jūsu projektos? Dalieties savās atziņās un piemēros zemāk!